home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / C / Applications / Python 1.3.3 / Python 133 SRC / Demo / www / fmt.py < prev    next >
Text File  |  1996-03-12  |  15KB  |  622 lines

  1. # Text formatting abstractions
  2.  
  3.  
  4. import string
  5. import Para
  6.  
  7.  
  8. # A formatter back-end object has one method that is called by the formatter:
  9. # addpara(p), where p is a paragraph object.  For example:
  10.  
  11.  
  12. # Formatter back-end to do nothing at all with the paragraphs
  13. class NullBackEnd:
  14.     #
  15.     def __init__(self):
  16.         pass
  17.     #
  18.     def addpara(self, p):
  19.         pass
  20.     #
  21.     def bgn_anchor(self, id):
  22.         pass
  23.     #
  24.     def end_anchor(self, id):
  25.         pass
  26.  
  27.  
  28. # Formatter back-end to collect the paragraphs in a list
  29. class SavingBackEnd(NullBackEnd):
  30.     #
  31.     def __init__(self):
  32.         self.paralist = []
  33.     #
  34.     def addpara(self, p):
  35.         self.paralist.append(p)
  36.     #
  37.     def hitcheck(self, h, v):
  38.         hits = []
  39.         for p in self.paralist:
  40.             if p.top <= v <= p.bottom:
  41.                 for id in p.hitcheck(h, v):
  42.                     if id not in hits:
  43.                         hits.append(id)
  44.         return hits
  45.     #
  46.     def extract(self):
  47.         text = ''
  48.         for p in self.paralist:
  49.             text = text + (p.extract())
  50.         return text
  51.     #
  52.     def extractpart(self, long1, long2):
  53.         if long1 > long2: long1, long2 = long2, long1
  54.         para1, pos1 = long1
  55.         para2, pos2 = long2
  56.         text = ''
  57.         while para1 < para2:
  58.             ptext = self.paralist[para1].extract()
  59.             text = text + ptext[pos1:]
  60.             pos1 = 0
  61.             para1 = para1 + 1
  62.         ptext = self.paralist[para2].extract()
  63.         return text + ptext[pos1:pos2]
  64.     #
  65.     def whereis(self, d, h, v):
  66.         total = 0
  67.         for i in range(len(self.paralist)):
  68.             p = self.paralist[i]
  69.             result = p.whereis(d, h, v)
  70.             if result <> None:
  71.                 return i, result
  72.         return None
  73.     #
  74.     def roundtowords(self, long1, long2):
  75.         i, offset = long1
  76.         text = self.paralist[i].extract()
  77.         while offset > 0 and text[offset-1] <> ' ': offset = offset-1
  78.         long1 = i, offset
  79.         #
  80.         i, offset = long2
  81.         text = self.paralist[i].extract()
  82.         n = len(text)
  83.         while offset < n-1 and text[offset] <> ' ': offset = offset+1
  84.         long2 = i, offset
  85.         #
  86.         return long1, long2
  87.     #
  88.     def roundtoparagraphs(self, long1, long2):
  89.         long1 = long1[0], 0
  90.         long2 = long2[0], len(self.paralist[long2[0]].extract())
  91.         return long1, long2
  92.  
  93.  
  94. # Formatter back-end to send the text directly to the drawing object
  95. class WritingBackEnd(NullBackEnd):
  96.     #
  97.     def __init__(self, d, width):
  98.         self.d = d
  99.         self.width = width
  100.         self.lineno = 0
  101.     #
  102.     def addpara(self, p):
  103.         self.lineno = p.render(self.d, 0, self.lineno, self.width)
  104.  
  105.  
  106. # A formatter receives a stream of formatting instructions and assembles
  107. # these into a stream of paragraphs on to a back-end.  The assembly is
  108. # parametrized by a text measurement object, which must match the output
  109. # operations of the back-end.  The back-end is responsible for splitting
  110. # paragraphs up in lines of a given maximum width.  (This is done because
  111. # in a windowing environment, when the window size changes, there is no
  112. # need to redo the assembly into paragraphs, but the splitting into lines
  113. # must be done taking the new window size into account.)
  114.  
  115.  
  116. # Formatter base class.  Initialize it with a text measurement object,
  117. # which is used for text measurements, and a back-end object,
  118. # which receives the completed paragraphs.  The formatting methods are:
  119. # setfont(font)
  120. # setleftindent(nspaces)
  121. # setjust(type) where type is 'l', 'c', 'r', or 'lr'
  122. # flush()
  123. # vspace(nlines)
  124. # needvspace(nlines)
  125. # addword(word, nspaces)
  126. class BaseFormatter:
  127.     #
  128.     def __init__(self, d, b):
  129.         # Drawing object used for text measurements
  130.         self.d = d
  131.         #
  132.         # BackEnd object receiving completed paragraphs
  133.         self.b = b
  134.         #
  135.         # Parameters of the formatting model
  136.         self.leftindent = 0
  137.         self.just = 'l'
  138.         self.font = None
  139.         self.blanklines = 0
  140.         #
  141.         # Parameters derived from the current font
  142.         self.space = d.textwidth(' ')
  143.         self.line = d.lineheight()
  144.         self.ascent = d.baseline()
  145.         self.descent = self.line - self.ascent
  146.         #
  147.         # Parameter derived from the default font
  148.         self.n_space = self.space
  149.         #
  150.         # Current paragraph being built
  151.         self.para = None
  152.         self.nospace = 1
  153.         #
  154.         # Font to set on the next word
  155.         self.nextfont = None
  156.     #
  157.     def newpara(self):
  158.         return Para.Para()
  159.     #
  160.     def setfont(self, font):
  161.         if font == None: return
  162.         self.font = self.nextfont = font
  163.         d = self.d
  164.         d.setfont(font)
  165.         self.space = d.textwidth(' ')
  166.         self.line = d.lineheight()
  167.         self.ascent = d.baseline()
  168.         self.descent = self.line - self.ascent
  169.     #
  170.     def setleftindent(self, nspaces):
  171.         self.leftindent = int(self.n_space * nspaces)
  172.         if self.para:
  173.             hang = self.leftindent - self.para.indent_left
  174.             if hang > 0 and self.para.getlength() <= hang:
  175.                 self.para.makehangingtag(hang)
  176.                 self.nospace = 1
  177.             else:
  178.                 self.flush()
  179.     #
  180.     def setrightindent(self, nspaces):
  181.         self.rightindent = int(self.n_space * nspaces)
  182.         if self.para:
  183.             self.para.indent_right = self.rightindent
  184.             self.flush()
  185.     #
  186.     def setjust(self, just):
  187.         self.just = just
  188.         if self.para:
  189.             self.para.just = self.just
  190.     #
  191.     def flush(self):
  192.         if self.para:
  193.             self.b.addpara(self.para)
  194.             self.para = None
  195.             if self.font <> None:
  196.                 self.d.setfont(self.font)
  197.         self.nospace = 1
  198.     #
  199.     def vspace(self, nlines):
  200.         self.flush()
  201.         if nlines > 0:
  202.             self.para = self.newpara()
  203.             tuple = None, '', 0, 0, 0, int(nlines*self.line), 0
  204.             self.para.words.append(tuple)
  205.             self.flush()
  206.             self.blanklines = self.blanklines + nlines
  207.     #
  208.     def needvspace(self, nlines):
  209.         self.flush() # Just to be sure
  210.         if nlines > self.blanklines:
  211.             self.vspace(nlines - self.blanklines)
  212.     #
  213.     def addword(self, text, space):
  214.         if self.nospace and not text:
  215.             return
  216.         self.nospace = 0
  217.         self.blanklines = 0
  218.         if not self.para:
  219.             self.para = self.newpara()
  220.             self.para.indent_left = self.leftindent
  221.             self.para.just = self.just
  222.             self.nextfont = self.font
  223.         space = int(space * self.space)
  224.         self.para.words.append(self.nextfont, text, \
  225.             self.d.textwidth(text), space, space, \
  226.             self.ascent, self.descent)
  227.         self.nextfont = None
  228.     #
  229.     def bgn_anchor(self, id):
  230.         if not self.para:
  231.             self.nospace = 0
  232.             self.addword('', 0)
  233.         self.para.bgn_anchor(id)
  234.     #
  235.     def end_anchor(self, id):
  236.         if not self.para:
  237.             self.nospace = 0
  238.             self.addword('', 0)
  239.         self.para.end_anchor(id)
  240.  
  241.  
  242. # Measuring object for measuring text as viewed on a tty
  243. class NullMeasurer:
  244.     #
  245.     def __init__(self):
  246.         pass
  247.     #
  248.     def setfont(self, font):
  249.         pass
  250.     #
  251.     def textwidth(self, text):
  252.         return len(text)
  253.     #
  254.     def lineheight(self):
  255.         return 1
  256.     #
  257.     def baseline(self):
  258.         return 0
  259.  
  260.  
  261. # Drawing object for writing plain ASCII text to a file
  262. class FileWriter:
  263.     #
  264.     def __init__(self, fp):
  265.         self.fp = fp
  266.         self.lineno, self.colno = 0, 0
  267.     #
  268.     def setfont(self, font):
  269.         pass
  270.     #
  271.     def text(self, (h, v), str):
  272.         if not str: return
  273.         if '\n' in str:
  274.             raise ValueError, 'can\'t write \\n'
  275.         while self.lineno < v:
  276.             self.fp.write('\n')
  277.             self.colno, self.lineno = 0, self.lineno + 1
  278.         while self.lineno > v:
  279.             # XXX This should never happen...
  280.             self.fp.write('\033[A') # ANSI up arrow
  281.             self.lineno = self.lineno - 1
  282.         if self.colno < h:
  283.             self.fp.write(' ' * (h - self.colno))
  284.         elif self.colno > h:
  285.             self.fp.write('\b' * (self.colno - h))
  286.         self.colno = h
  287.         self.fp.write(str)
  288.         self.colno = h + len(str)
  289.  
  290.  
  291. # Formatting class to do nothing at all with the data
  292. class NullFormatter(BaseFormatter):
  293.     #
  294.     def __init__(self):
  295.         d = NullMeasurer()
  296.         b = NullBackEnd()
  297.         BaseFormatter.__init__(self, d, b)
  298.  
  299.  
  300. # Formatting class to write directly to a file
  301. class WritingFormatter(BaseFormatter):
  302.     #
  303.     def __init__(self, fp, width):
  304.         dm = NullMeasurer()
  305.         dw = FileWriter(fp)
  306.         b = WritingBackEnd(dw, width)
  307.         BaseFormatter.__init__(self, dm, b)
  308.         self.blanklines = 1
  309.     #
  310.     # Suppress multiple blank lines
  311.     def needvspace(self, nlines):
  312.         BaseFormatter.needvspace(self, min(1, nlines))
  313.  
  314.  
  315. # A "FunnyFormatter" writes ASCII text with a twist: *bold words*,
  316. # _italic text_ and _underlined words_, and `quoted text'.
  317. # It assumes that the fonts are 'r', 'i', 'b', 'u', 'q': (roman,
  318. # italic, bold, underline, quote).
  319. # Moreover, if the font is in upper case, the text is converted to
  320. # UPPER CASE.
  321. class FunnyFormatter(WritingFormatter):
  322.     #
  323.     def flush(self):
  324.         if self.para: finalize(self.para)
  325.         WritingFormatter.flush(self)
  326.  
  327.  
  328. # Surrounds *bold words* and _italic text_ in a paragraph with
  329. # appropriate markers, fixing the size (assuming these characters'
  330. # width is 1).
  331. openchar = \
  332.     {'b':'*', 'i':'_', 'u':'_', 'q':'`', 'B':'*', 'I':'_', 'U':'_', 'Q':'`'}
  333. closechar = \
  334.     {'b':'*', 'i':'_', 'u':'_', 'q':'\'', 'B':'*', 'I':'_', 'U':'_', 'Q':'\''}
  335. def finalize(para):
  336.     oldfont = curfont = 'r'
  337.     para.words.append('r', '', 0, 0, 0, 0) # temporary, deleted at end
  338.     for i in range(len(para.words)):
  339.         fo, te, wi = para.words[i][:3]
  340.         if fo <> None: curfont = fo
  341.         if curfont <> oldfont:
  342.             if closechar.has_key(oldfont):
  343.                 c = closechar[oldfont]
  344.                 j = i-1
  345.                 while j > 0 and para.words[j][1] == '': j = j-1
  346.                 fo1, te1, wi1 = para.words[j][:3]
  347.                 te1 = te1 + c
  348.                 wi1 = wi1 + len(c)
  349.                 para.words[j] = (fo1, te1, wi1) + \
  350.                     para.words[j][3:]
  351.             if openchar.has_key(curfont) and te:
  352.                 c = openchar[curfont]
  353.                 te = c + te
  354.                 wi = len(c) + wi
  355.                 para.words[i] = (fo, te, wi) + \
  356.                     para.words[i][3:]
  357.             if te: oldfont = curfont
  358.             else: oldfont = 'r'
  359.         if curfont in string.uppercase:
  360.             te = string.upper(te)
  361.             para.words[i] = (fo, te, wi) + para.words[i][3:]
  362.     del para.words[-1]
  363.  
  364.  
  365. # Formatter back-end to draw the text in a window.
  366. # This has an option to draw while the paragraphs are being added,
  367. # to minimize the delay before the user sees anything.
  368. # This manages the entire "document" of the window.
  369. class StdwinBackEnd(SavingBackEnd):
  370.     #
  371.     def __init__(self, window, drawnow):
  372.         self.window = window
  373.         self.drawnow = drawnow
  374.         self.width = window.getwinsize()[0]
  375.         self.selection = None
  376.         self.height = 0
  377.         window.setorigin(0, 0)
  378.         window.setdocsize(0, 0)
  379.         self.d = window.begindrawing()
  380.         SavingBackEnd.__init__(self)
  381.     #
  382.     def finish(self):
  383.         self.d.close()
  384.         self.d = None
  385.         self.window.setdocsize(0, self.height)
  386.     #
  387.     def addpara(self, p):
  388.         self.paralist.append(p)
  389.         if self.drawnow:
  390.             self.height = \
  391.                 p.render(self.d, 0, self.height, self.width)
  392.         else:
  393.             p.layout(self.width)
  394.             p.left = 0
  395.             p.top = self.height
  396.             p.right = self.width
  397.             p.bottom = self.height + p.height
  398.             self.height = p.bottom
  399.     #
  400.     def resize(self):
  401.         self.window.change((0, 0), (self.width, self.height))
  402.         self.width = self.window.getwinsize()[0]
  403.         self.height = 0
  404.         for p in self.paralist:
  405.             p.layout(self.width)
  406.             p.left = 0
  407.             p.top = self.height
  408.             p.right = self.width
  409.             p.bottom = self.height + p.height
  410.             self.height = p.bottom
  411.         self.window.change((0, 0), (self.width, self.height))
  412.         self.window.setdocsize(0, self.height)
  413.     #
  414.     def redraw(self, area):
  415.         d = self.window.begindrawing()
  416.         (left, top), (right, bottom) = area
  417.         d.erase(area)
  418.         d.cliprect(area)
  419.         for p in self.paralist:
  420.             if top < p.bottom and p.top < bottom:
  421.                 v = p.render(d, p.left, p.top, p.right)
  422.         if self.selection:
  423.             self.invert(d, self.selection)
  424.         d.close()
  425.     #
  426.     def setselection(self, new):
  427.         if new:
  428.             long1, long2 = new
  429.             pos1 = long1[:3]
  430.             pos2 = long2[:3]
  431.             new = pos1, pos2
  432.         if new <> self.selection:
  433.             d = self.window.begindrawing()
  434.             if self.selection:
  435.                 self.invert(d, self.selection)
  436.             if new:
  437.                 self.invert(d, new)
  438.             d.close()
  439.             self.selection = new
  440.     #
  441.     def getselection(self):
  442.         return self.selection
  443.     #
  444.     def extractselection(self):
  445.         if self.selection:
  446.             a, b = self.selection
  447.             return self.extractpart(a, b)
  448.         else:
  449.             return None
  450.     #
  451.     def invert(self, d, region):
  452.         long1, long2 = region
  453.         if long1 > long2: long1, long2 = long2, long1
  454.         para1, pos1 = long1
  455.         para2, pos2 = long2
  456.         while para1 < para2:
  457.             self.paralist[para1].invert(d, pos1, None)
  458.             pos1 = None
  459.             para1 = para1 + 1
  460.         self.paralist[para2].invert(d, pos1, pos2)
  461.     #
  462.     def search(self, prog):
  463.         import regex, string
  464.         if type(prog) == type(''):
  465.             prog = regex.compile(string.lower(prog))
  466.         if self.selection:
  467.             iold = self.selection[0][0]
  468.         else:
  469.             iold = -1
  470.         hit = None
  471.         for i in range(len(self.paralist)):
  472.             if i == iold or i < iold and hit:
  473.                 continue
  474.             p = self.paralist[i]
  475.             text = string.lower(p.extract())
  476.             if prog.search(text) >= 0:
  477.                 a, b = prog.regs[0]
  478.                 long1 = i, a
  479.                 long2 = i, b
  480.                 hit = long1, long2
  481.                 if i > iold:
  482.                     break
  483.         if hit:
  484.             self.setselection(hit)
  485.             i = hit[0][0]
  486.             p = self.paralist[i]
  487.             self.window.show((p.left, p.top), (p.right, p.bottom))
  488.             return 1
  489.         else:
  490.             return 0
  491.     #
  492.     def showanchor(self, id):
  493.         for i in range(len(self.paralist)):
  494.             p = self.paralist[i]
  495.             if p.hasanchor(id):
  496.                 long1 = i, 0
  497.                 long2 = i, len(p.extract())
  498.                 hit = long1, long2
  499.                 self.setselection(hit)
  500.                 self.window.show( \
  501.                     (p.left, p.top), (p.right, p.bottom))
  502.                 break
  503.  
  504.  
  505. # GL extensions
  506.  
  507. class GLFontCache:
  508.     #
  509.     def __init__(self):
  510.         self.reset()
  511.         self.setfont('')
  512.     #
  513.     def reset(self):
  514.         self.fontkey = None
  515.         self.fonthandle = None
  516.         self.fontinfo = None
  517.         self.fontcache = {}
  518.     #
  519.     def close(self):
  520.         self.reset()
  521.     #
  522.     def setfont(self, fontkey):
  523.         if fontkey == '':
  524.             fontkey = 'Times-Roman 12'
  525.         elif ' ' not in fontkey:
  526.             fontkey = fontkey + ' 12'
  527.         if fontkey == self.fontkey:
  528.             return
  529.         if self.fontcache.has_key(fontkey):
  530.             handle = self.fontcache[fontkey]
  531.         else:
  532.             import string
  533.             i = string.index(fontkey, ' ')
  534.             name, sizestr = fontkey[:i], fontkey[i:]
  535.             size = eval(sizestr)
  536.             key1 = name + ' 1'
  537.             key = name + ' ' + `size`
  538.             # NB key may differ from fontkey!
  539.             if self.fontcache.has_key(key):
  540.                 handle = self.fontcache[key]
  541.             else:
  542.                 if self.fontcache.has_key(key1):
  543.                     handle = self.fontcache[key1]
  544.                 else:
  545.                     import fm
  546.                     handle = fm.findfont(name)
  547.                     self.fontcache[key1] = handle
  548.                 handle = handle.scalefont(size)
  549.                 self.fontcache[fontkey] = \
  550.                     self.fontcache[key] = handle
  551.         self.fontkey = fontkey
  552.         if self.fonthandle <> handle:
  553.             self.fonthandle = handle
  554.             self.fontinfo = handle.getfontinfo()
  555.             handle.setfont()
  556.  
  557.  
  558. class GLMeasurer(GLFontCache):
  559.     #
  560.     def textwidth(self, text):
  561.         return self.fonthandle.getstrwidth(text)
  562.     #
  563.     def baseline(self):
  564.         return self.fontinfo[6] - self.fontinfo[3]
  565.     #
  566.     def lineheight(self):
  567.         return self.fontinfo[6]
  568.  
  569.  
  570. class GLWriter(GLFontCache):
  571.     #
  572.     # NOTES:
  573.     # (1) Use gl.ortho2 to use X pixel coordinates!
  574.     #
  575.     def text(self, (h, v), text):
  576.         import gl, fm
  577.         gl.cmov2i(h, v + self.fontinfo[6] - self.fontinfo[3])
  578.         fm.prstr(text)
  579.     #
  580.     def setfont(self, fontkey):
  581.         oldhandle = self.fonthandle
  582.         GLFontCache.setfont(fontkey)
  583.         if self.fonthandle <> oldhandle:
  584.             handle.setfont()
  585.  
  586.  
  587. class GLMeasurerWriter(GLMeasurer, GLWriter):
  588.     pass
  589.  
  590.  
  591. class GLBackEnd(SavingBackEnd):
  592.     #
  593.     def __init__(self, wid):
  594.         import gl
  595.         gl.winset(wid)
  596.         self.wid = wid
  597.         self.width = gl.getsize()[1]
  598.         self.height = 0
  599.         self.d = GLMeasurerWriter()
  600.         SavingBackEnd.__init__(self)
  601.     #
  602.     def finish(self):
  603.         pass
  604.     #
  605.     def addpara(self, p):
  606.         self.paralist.append(p)
  607.         self.height = p.render(self.d, 0, self.height, self.width)
  608.     #
  609.     def redraw(self):
  610.         import gl
  611.         gl.winset(self.wid)
  612.         width = gl.getsize()[1]
  613.         if width <> self.width:
  614.             setdocsize = 1
  615.             self.width = width
  616.             for p in self.paralist:
  617.                 p.top = p.bottom = None
  618.         d = self.d
  619.         v = 0
  620.         for p in self.paralist:
  621.             v = p.render(d, 0, v, width)
  622.